import {
  AiPrompts, Rulebook, GameTurn, AiScenarioResponse, DynamicGameState,
  ApiChannel, ModelConfig, GameSettings, AiFullResponse, AiScenarioAndKeyInfoResponse,
  AiHybridStructuredDataResponse,
  GuidingAiPlan,
  MacroFunction,
  JsonPatchOperation,
  MacroScope,
  FunctionCallingTurnResponse,
  MacroFunctionParameter,
  TurnUpdateCallback
} from '../types';
import {
  SCENARIO_PROMPT_GENERATION_INSTRUCTION,
  STATUS_PANEL_PROMPT_GENERATION_INSTRUCTION,
  KEY_INFO_EXTRACTION_PROMPT_GENERATION_INSTRUCTION,
  AI_GAME_TURN_SYSTEM_INSTRUCTION_TEMPLATE,
  NARRATIVE_AND_KEY_INFO_GENERATION_TEMPLATE,
  NARRATIVE_GENERATION_TEMPLATE,
  STATUS_PANEL_UPDATE_TEMPLATE,
  KEY_INFO_EXTRACTION_TEMPLATE,
  HYBRID_NARRATIVE_STREAM_TEMPLATE,
  HYBRID_STRUCTURED_DATA_TEMPLATE,
  DEFAULT_API_KEY_PLACEHOLDER,
  DEFAULT_REQUEST_TIMEOUT,
  CALCULATION_HELPER_AI_TEMPLATE,
  NARRATIVE_AI_WITH_CALCS_TEMPLATE,
  TOOL_CALLING_TEMPLATE,
  AI_SINGLE_PROMPT_REFINEMENT_SYSTEM_INSTRUCTION,
  RULEBOOK_REFINEMENT_SYSTEM_INSTRUCTION
} from '../constants';
import { JSONSchema7 } from 'json-schema';
// FIX: Import 'applyJsonPatch' to resolve 'Cannot find name' error.
import { applyJsonPatch } from '../utils/jsonPatch';

// Custom error for parsing failures, carrying the raw response text
export class ParseError extends Error {
    constructor(message: string, public rawText: string) {
        super(message);
        this.name = 'ParseError';
    }
}

// Helper to find configurations; these could also be passed directly to functions
export function getModelAndChannel(settings: GameSettings, modelId: string | null): { model: ModelConfig | null, channel: ApiChannel | null } {
    if (!modelId) return { model: null, channel: null };
    const model = settings.modelConfigs.find(m => m.id === modelId) || null;
    if (!model) return { model: null, channel: null };
    const channel = settings.apiChannels.find(c => c.id === model.apiChannelId) || null;
    return { model, channel };
}


async function fetchWithAuth(
    channel: ApiChannel,
    model: ModelConfig,
    messages: { role: string, content: string | null, tool_calls?: any[], tool_call_id?: string }[],
    tools: any[] | undefined, // New: for function calling
    isImageGeneration: boolean = false,
    imagePrompt?: string,
    corsProxyUrl?: string,
    requestTimeout: number = DEFAULT_REQUEST_TIMEOUT
): Promise<any> {
  if (!channel.baseUrl) {
    throw new Error("API Base URL尚未在选定渠道中配置。");
  }
  if (!channel.apiKey || channel.apiKey === DEFAULT_API_KEY_PLACEHOLDER) {
    throw new Error("API Key尚未在选定渠道中配置或无效。");
  }

  const requestUrl = corsProxyUrl ? corsProxyUrl + channel.baseUrl : channel.baseUrl;

  const headers: HeadersInit = {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${channel.apiKey}`, // Common standard, user's API must support this
  };

  let bodyPayload: any;

  if (isImageGeneration) {
    bodyPayload = {
        prompt: imagePrompt, // The image prompt
        model: model.apiModelName, // The specific model identifier for the API
        n: 1, // Number of images to generate
        response_format: "b64_json", // Requesting base64 encoded image
    };
  } else {
    // Assuming all modern text APIs use chat completions format
    if (channel.baseUrl.includes("chat/completions")) {
        bodyPayload = {
            model: model.apiModelName,
            messages: messages,
            temperature: model.temperature,
            max_tokens: model.maxTokens,
            stream: false, // Explicitly false for non-streaming
        };
        if (tools && tools.length > 0) {
            bodyPayload.tools = tools.map(t => ({ type: 'function', function: t }));
            bodyPayload.tool_choice = "auto";
        }
    } else {
        // Fallback for older "prompt" based structure, might not support function calling
        const simplePrompt = messages.map(m => `${m.role}: ${m.content}`).join('\n');
        bodyPayload = {
            prompt: simplePrompt,
            model: model.apiModelName,
            temperature: model.temperature,
            max_tokens: model.maxTokens,
            stream: false, 
        };
    }
  }

  const body = JSON.stringify(bodyPayload);
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), requestTimeout);

  try {
    const response = await fetch(requestUrl, { method: 'POST', headers, body, signal: controller.signal });
    clearTimeout(timeoutId);

    if (!response.ok) {
      let errorData;
      try {
        errorData = await response.json();
      } catch (e) {
        errorData = await response.text(); // Fallback to text if JSON parsing fails
      }
      console.error("API请求失败:", response.status, errorData);
      throw new Error(`API 请求失败: ${response.status} - ${typeof errorData === 'string' ? errorData : JSON.stringify(errorData)}`);
    }

    const responseText = await response.text();
    try {
      return JSON.parse(responseText);
    } catch (e) {
      if (!isImageGeneration && typeof responseText === 'string') {
        return responseText;
      }
      console.error("解析API返回的JSON失败 (isImageGeneration: " + isImageGeneration + "):", e, "Raw text:", responseText.substring(0, 500));
      throw new ParseError(`API返回的格式无效，期望JSON但收到非JSON文本: ${responseText.substring(0,100)}...`, responseText);
    }

  } catch (error) {
    clearTimeout(timeoutId);
    if (error instanceof ParseError) {
        throw error; // Re-throw the custom ParseError to be caught by the caller
    }
    if (error instanceof Error && error.name === 'AbortError') {
        throw new Error(`API 请求超时 (超过 ${requestTimeout / 1000} 秒)`);
    }
    
    let errorMessage = `调用API (${requestUrl}) 时发生网络错误。这可能是由于：
- 网络连接问题。
- API Base URL (${channel.baseUrl}) ${corsProxyUrl ? `或代理 (${corsProxyUrl}) ` : ''}不正确、不完整或服务器未运行。请确保它指向一个有效的POST端点。
- 跨域资源共享 (CORS) 策略阻止了请求。使用网络兼容性代理可能是解决方案。
- 如果API需要特定路径 (例如 /v1/completions, /api/generate)，请确保它包含在API渠道设置的Base URL中。`;

    if (error instanceof Error && error.message.toLowerCase().includes("failed to fetch")) {
        errorMessage += `\n细节: ${error.message}`;
        throw new Error(errorMessage);
    } else if (error instanceof Error) { // Keep the original error message if it's not "failed to fetch"
        throw new Error(`${errorMessage}\n细节: ${error.message}`);
    }
    throw new Error(errorMessage); // Fallback for non-Error objects
  }
}

async function* fetchStreamWithAuth(
    channel: ApiChannel,
    model: ModelConfig,
    messages: { role: string; content: string }[],
    corsProxyUrl?: string,
    requestTimeout: number = DEFAULT_REQUEST_TIMEOUT
): AsyncGenerator<string> {
    if (!channel.baseUrl) {
        throw new Error("API Base URL尚未在选定渠道中配置。");
    }
    if (!channel.apiKey || channel.apiKey === DEFAULT_API_KEY_PLACEHOLDER) {
        throw new Error("API Key尚未在选定渠道中配置或无效。");
    }

    const requestUrl = corsProxyUrl ? corsProxyUrl + channel.baseUrl : channel.baseUrl;

    const headers: HeadersInit = {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${channel.apiKey}`,
    };

    let bodyPayload: any;
    if (channel.baseUrl.includes("chat/completions")) {
        bodyPayload = {
            model: model.apiModelName,
            messages: messages,
            temperature: model.temperature,
            max_tokens: model.maxTokens,
            stream: true,
        };
    } else {
        throw new Error("流式传输目前仅支持 'chat/completions' 类型的API端点。");
    }

    const body = JSON.stringify(bodyPayload);
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), requestTimeout);

    try {
        const response = await fetch(requestUrl, { method: 'POST', headers, body, signal: controller.signal });
        clearTimeout(timeoutId);

        if (!response.ok) {
            let errorData;
            try {
                errorData = await response.json();
            } catch (e) {
                errorData = await response.text();
            }
            console.error("API流式请求失败:", response.status, errorData);
            throw new Error(`API 流式请求失败: ${response.status} - ${typeof errorData === 'string' ? errorData : JSON.stringify(errorData)}`);
        }

        if (!response.body) {
            throw new Error('API响应中没有正文。');
        }

        const reader = response.body.getReader();
        const decoder = new TextDecoder("utf-8");
        let buffer = "";

        while (true) {
            const { done, value } = await reader.read();
            if (done) {
                break;
            }

            buffer += decoder.decode(value, { stream: true });
            const lines = buffer.split("\n");
            buffer = lines.pop() || ""; // Keep the last, potentially incomplete line

            for (const line of lines) {
                if (line.trim() === "") continue;
                if (line.includes("data: [DONE]")) return;
                if (line.startsWith("data: ")) {
                    const data = line.substring(6);
                    try {
                        const parsed = JSON.parse(data);
                        const textChunk = parsed.choices?.[0]?.delta?.content;
                        if (textChunk) {
                            yield textChunk;
                        }
                    } catch (e) {
                        console.error("解析流式数据块失败:", e, "Data:", data);
                    }
                }
            }
        }
    } catch (error) {
        clearTimeout(timeoutId);
        if (error instanceof Error && error.name === 'AbortError') {
            throw new Error(`API 流式请求超时 (超过 ${requestTimeout / 1000} 秒)`);
        }
        if (error instanceof Error) {
            throw new Error(`调用流式API时出错: ${error.message}`);
        }
        throw new Error("调用流式API时发生未知网络错误。");
    }
}


function parseJsonFromAiResponse(data: any, expectedTopLevelKeys?: string[]): any {
  let contentString: string | null = null;
  let jsonData: any = data; // Assume data is JSON object by default

  // Step 1: Extract the core content string from various possible AI response structures.
  if (typeof data === 'object' && data !== null) {
    if (data.choices && Array.isArray(data.choices) && data.choices.length > 0) {
      const firstChoice = data.choices[0];
      if (firstChoice.message && firstChoice.message.content) {
        const content = firstChoice.message.content;
        if (typeof content === 'string') {
          contentString = content;
        } else {
          jsonData = content; // Already a parsed JSON object
        }
      } else if (typeof firstChoice === 'object' && 'text' in firstChoice && typeof firstChoice.text === 'string') { // Fallback for other structures
        contentString = firstChoice.text;
      } else if (typeof firstChoice === 'object') {
        jsonData = firstChoice;
      }
    }
    // If not a standard chat response, jsonData remains `data`
  } else if (typeof data === 'string') {
    contentString = data;
  } else {
    throw new Error(`AI返回了无法解析的类型: ${typeof data}`);
  }
  
  // Step 2: If we have a string, attempt to parse it with increasing robustness.
  if (contentString) {
    let jsonStr = contentString.trim();
    // For function calling, the JSON can be inside a code block at the end.
    const fenceRegex = /```json\s*([\s\S]*?)\s*```$/;
    const match = jsonStr.match(fenceRegex);

    if (match && match[1]) {
      // If a fence is found, we assume it's the definitive JSON part.
      try {
        jsonData = JSON.parse(match[1].trim());
      } catch (e) {
        throw new ParseError(`无法解析AI响应中的JSON代码块。`, contentString);
      }
    } else {
      // If no fence, try to parse the whole string or extract from it.
      let parsed = false;
      try {
        jsonData = JSON.parse(jsonStr);
        parsed = true;
      } catch (e) { /* ignore and proceed */ }

      if (!parsed) {
          const firstBrace = jsonStr.indexOf('{');
          const lastBrace = jsonStr.lastIndexOf('}');
          const firstBracket = jsonStr.indexOf('[');
          const lastBracket = jsonStr.lastIndexOf(']');
          let start = -1, end = -1;
          if (firstBrace !== -1 && (firstBracket === -1 || firstBrace < firstBracket)) {
              start = firstBrace;
              end = lastBrace;
          } else if (firstBracket !== -1) {
              start = firstBracket;
              end = lastBracket;
          }

          if (start !== -1 && end > start) {
              const potentialJson = jsonStr.substring(start, end + 1);
              try {
                  jsonData = JSON.parse(potentialJson);
                  parsed = true;
              } catch (e) { /* ignore */ }
          }
      }

      if (!parsed && expectedTopLevelKeys && expectedTopLevelKeys.length > 0) {
        throw new ParseError(`AI响应内容不是预期的有效JSON，即便在尝试提取后也是如此。`, contentString);
      } else if (!parsed) {
        return contentString; // It's likely plain text
      }
    }
  }

  // Step 3: Validate that the parsed JSON has the expected structure.
  if (expectedTopLevelKeys && typeof jsonData === 'object' && jsonData !== null) {
    for (const key of expectedTopLevelKeys) {
      if (!(key in jsonData)) {
        console.error(`AI返回的JSON缺少预期的顶级键: "${key}". 收到的数据:`, jsonData);
        throw new Error(`AI返回的JSON缺少预期的顶级键: "${key}".`);
      }
    }
  } else if (expectedTopLevelKeys && (typeof jsonData !== 'object' || jsonData === null)) {
     console.error(`AI响应解析后不是预期的对象结构，但期望顶级键: ${expectedTopLevelKeys.join(', ')}. 解析结果:`, jsonData);
     throw new Error(`AI响应解析后不是预期的对象结构，但期望顶级键。`);
  }

  return jsonData;
}


function formatGameHistoryForPrompt(history: GameTurn[], forDisplay: boolean = false): string {
  return history.map(turn => {
    let actionText = forDisplay ? "AI 推进/初始" : "未知行动";
    if (turn.customPlayerInput) {
      actionText = `玩家自定义输入: ${turn.customPlayerInput}`;
    } else if (turn.chosenOptionIndex !== undefined && turn.options[turn.chosenOptionIndex]) {
      actionText = `玩家选择: ${turn.options[turn.chosenOptionIndex]}`;
    } else if (forDisplay && turn.options.length > 0 && turn.chosenOptionIndex === undefined && turn.id.startsWith("turnuuid")) {
      actionText = `AI提供了选项，但玩家未选择`;
    }

    // Only include the state snapshot for display purposes, not for the AI prompt context,
    // as the most recent state is already provided separately.
    const stateSnapshotString = (forDisplay && turn.turnStateSnapshot && Object.keys(turn.turnStateSnapshot).length > 0)
      ? `当时状态: ${JSON.stringify(turn.turnStateSnapshot)}\n`
      : '';

    return `${stateSnapshotString}${actionText}\nAI叙述: ${turn.narrative}\n---`;
  }).join('\n');
}


export async function generateInitialAiPrompts(
  rulebook: Rulebook,
  channel: ApiChannel,
  model: ModelConfig,
  corsProxyUrl?: string,
  requestTimeout?: number,
  onProgress?: (message: string) => void
): Promise<AiPrompts> {
  const callForPrompt = async (instructionTemplate: string, promptName: string): Promise<string> => {
    onProgress?.(`正在生成 ${promptName}...`);
    const prompt = instructionTemplate
      .replace('{gameName}', rulebook.gameName)
      .replace('{backgroundStory}', rulebook.backgroundStory)
      .replace('{rules}', rulebook.rules);

    const responseData = await fetchWithAuth(channel, model, [{ role: 'user', content: prompt }], undefined, false, undefined, corsProxyUrl, requestTimeout);
    
    // Directly extract the content string. `parseJsonFromAiResponse` is avoided
    // as it can mistakenly parse JSON examples from within the prompt text.
    const textContent = (responseData?.choices?.[0]?.message?.content ?? responseData?.choices?.[0]?.text) || (typeof responseData === 'string' ? responseData : null);

    if (textContent !== null) {
      return textContent.trim();
    }
    
    console.error(`AI未能为 ${promptName} 生成纯文本提示词:`, responseData);
    throw new Error(`AI未能为 ${promptName} 生成纯文本提示词。`);
  };

  try {
    const scenarioGenerationPrompt = await callForPrompt(SCENARIO_PROMPT_GENERATION_INSTRUCTION, "场景生成提示");
    const statusPanelPrompt = await callForPrompt(STATUS_PANEL_PROMPT_GENERATION_INSTRUCTION, "状态面板提示");
    const keyInfoExtractionPrompt = await callForPrompt(KEY_INFO_EXTRACTION_PROMPT_GENERATION_INSTRUCTION, "关键信息提取提示");

    onProgress?.("所有提示词已生成！");

    return {
      scenarioGenerationPrompt,
      statusPanelPrompt,
      keyInfoExtractionPrompt,
    } as AiPrompts;
  } catch (error) {
    console.error("生成初始AI提示词时出错:", error);
    if (error instanceof Error) {
        if (error instanceof ParseError) {
             throw new ParseError(`生成初始AI提示词失败: ${error.message}`, error.rawText);
        }
      throw new Error(`生成初始AI提示词失败: ${error.message}`);
    }
    throw new Error("生成初始AI提示词失败，发生未知错误。");
  }
}

export async function refineSingleAiPrompt(
  rulebook: Rulebook,
  currentPromptText: string,
  promptWritingGuide: string,
  userRequest: string,
  channel: ApiChannel,
  model: ModelConfig,
  corsProxyUrl?: string,
  requestTimeout?: number
): Promise<string> {
    const prompt = AI_SINGLE_PROMPT_REFINEMENT_SYSTEM_INSTRUCTION
      .replace('{gameName}', rulebook.gameName)
      .replace('{backgroundStory}', rulebook.backgroundStory)
      .replace('{rules}', rulebook.rules)
      .replace('{promptWritingGuide}', promptWritingGuide)
      .replace('{currentPromptText}', currentPromptText)
      .replace('{userRequest}', userRequest);
  
    try {
      const responseData = await fetchWithAuth(channel, model, [{ role: 'user', content: prompt }], undefined, false, undefined, corsProxyUrl, requestTimeout);
      const textContent = (responseData?.choices?.[0]?.message?.content ?? responseData?.choices?.[0]?.text) || (typeof responseData === 'string' ? responseData : null);

      if (textContent !== null) {
        return textContent.trim();
      } else {
        console.error("AI未能返回纯文本的优化后提示词:", responseData);
        throw new Error("AI未能返回纯文本的优化后提示词。");
      }
    } catch (error) {
      console.error("优化AI提示词时出错:", error);
      if (error instanceof Error) {
          if (error instanceof ParseError) {
               throw new ParseError(`优化AI提示词失败: ${error.message}`, error.rawText);
          }
        throw new Error(`优化AI提示词失败: ${error.message}`);
      }
      throw new Error("优化AI提示词失败，发生未知错误。");
    }
}

export async function refineRulebook(
  gameName: string,
  backgroundStory: string,
  currentRules: string,
  userRequest: string,
  channel: ApiChannel,
  model: ModelConfig,
  corsProxyUrl?: string,
  requestTimeout?: number
): Promise<string> {
    const prompt = RULEBOOK_REFINEMENT_SYSTEM_INSTRUCTION
      .replace('{gameName}', gameName)
      .replace('{backgroundStory}', backgroundStory)
      .replace('{currentRules}', currentRules || "请从零开始创建规则。")
      .replace('{userRequest}', userRequest);
  
    try {
      const responseData = await fetchWithAuth(channel, model, [{ role: 'user', content: prompt }], undefined, false, undefined, corsProxyUrl, requestTimeout);
      const textContent = (responseData?.choices?.[0]?.message?.content ?? responseData?.choices?.[0]?.text) || (typeof responseData === 'string' ? responseData : null);
  
      if (textContent !== null) {
        return textContent.trim();
      } else {
        console.error("AI未能返回纯文本的优化后规则书:", responseData);
        throw new Error("AI未能返回纯文本的优化后规则书。");
      }
    } catch (error) {
      console.error("优化规则书时出错:", error);
      if (error instanceof Error) {
          if (error instanceof ParseError) {
               throw new ParseError(`优化规则书失败: ${error.message}`, error.rawText);
          }
        throw new Error(`优化规则书失败: ${error.message}`);
      }
      throw new Error("优化规则书失败，发生未知错误。");
    }
}


export async function generateImage(
    prompt: string,
    channel: ApiChannel,
    model: ModelConfig,
    corsProxyUrl?: string,
    requestTimeout?: number
): Promise<string> { // Returns data URL or direct image URL

  try {
    const responseData = await fetchWithAuth(channel, model, [], undefined, true, prompt, corsProxyUrl, requestTimeout); // true for isImageGeneration

    let base64ImageBytes: string | undefined;

    if (responseData.base64Image) { 
        base64ImageBytes = responseData.base64Image;
    } else if (responseData.images && Array.isArray(responseData.images) && responseData.images.length > 0) {
        const firstImage = responseData.images[0];
        if (typeof firstImage === 'string') { 
            base64ImageBytes = firstImage;
        } else if (firstImage.b64_json) {
            base64ImageBytes = firstImage.b64_json;
        } else if (firstImage.base64) { 
            base64ImageBytes = firstImage.base64;
        } else if (firstImage.imageBytes) {
             base64ImageBytes = firstImage.imageBytes;
        }
    } else if (responseData.data && Array.isArray(responseData.data) && responseData.data.length > 0) { // OpenAI DALL-E like
        const firstItem = responseData.data[0];
        if (firstItem.b64_json) {
            base64ImageBytes = firstItem.b64_json;
        } else if (firstItem.url) { 
             console.log("Image API returned a URL, using it directly:", firstItem.url);
             return firstItem.url; 
        }
    } else if (responseData.imageBytes) { 
        base64ImageBytes = responseData.imageBytes;
    }


    if (base64ImageBytes) {
        if (base64ImageBytes.startsWith('data:image/')) { 
            return base64ImageBytes;
        }
        let mimeType = 'image/jpeg'; // Default
        // Very basic check, consider making mimeType configurable or improving detection
        if (base64ImageBytes.charAt(0) === '/') mimeType = 'image/jpeg'; 
        else if (base64ImageBytes.charAt(0) === 'i') mimeType = 'image/png';
        else if (base64ImageBytes.charAt(0) === 'R') mimeType = 'image/gif'; // GIF
        else if (base64ImageBytes.charAt(0) === 'U') mimeType = 'image/webp'; // WebP
        
        return `data:${mimeType};base64,${base64ImageBytes}`;
    } else {
        console.error("AI未能生成图像或返回了无法识别的数据结构:", responseData);
        throw new Error("AI未能生成图像或返回了无法识别的数据结构。请检查API响应。");
    }
  } catch (error) {
    console.error("生成图像时出错:", error);
    if (error instanceof Error) throw error;
    throw new Error("生成图像失败，发生未知错误。");
  }
}

// --- Single, Double, Triple Call Mode Logic ---

export async function generateTurnSingleCall(
  currentHistory: GameTurn[],
  playerAction: string,
  rulebook: Rulebook,
  aiPrompts: AiPrompts,
  aggregatedKeyInfo: string,
  maxHistoryLength: number,
  channel: ApiChannel,
  model: ModelConfig,
  currentStatusPanelContentString: string,
  corsProxyUrl?: string,
  requestTimeout?: number
): Promise<{ response: AiFullResponse, fullPrompt: string }> {
    const gameHistoryString = formatGameHistoryForPrompt(currentHistory.slice(-maxHistoryLength));
    const fullPrompt = AI_GAME_TURN_SYSTEM_INSTRUCTION_TEMPLATE
        .replace('{gameBackgroundStory}', rulebook.backgroundStory)
        .replace('{gameRules}', rulebook.rules)
        .replace('{scenarioGenerationPrompt}', aiPrompts.scenarioGenerationPrompt)
        .replace('{statusPanelPrompt}', aiPrompts.statusPanelPrompt)
        .replace('{keyInfoExtractionPrompt}', aiPrompts.keyInfoExtractionPrompt)
        .replace('{currentStatusPanelContentString}', currentStatusPanelContentString)
        .replace('{aggregatedKeyInfo}', aggregatedKeyInfo || "暂无关键信息。")
        .replace('{maxHistoryLengthToDisplay}', String(maxHistoryLength))
        .replace('{gameHistoryString}', gameHistoryString || "这是游戏的开始。")
        .replace('{playerAction}', playerAction);
        
    const responseData = await fetchWithAuth(channel, model, [{ role: 'user', content: fullPrompt }], undefined, false, undefined, corsProxyUrl, requestTimeout);
    const parsed = parseJsonFromAiResponse(responseData, ["scenario", "statusPanelContent", "keyInfoUpdate"]);
    
    if (!parsed.scenario || !parsed.scenario.narrative || !Array.isArray(parsed.scenario.options) || !parsed.statusPanelContent || typeof parsed.keyInfoUpdate !== 'string') {
      console.error("Single call response missing required fields:", parsed);
      throw new Error("AI返回的JSON结构不完整或无效。");
    }
    
    return { response: parsed as AiFullResponse, fullPrompt };
}

export async function generateNarrativeAndKeyInfo(
  currentHistory: GameTurn[],
  playerAction: string,
  rulebook: Rulebook,
  aiPrompts: AiPrompts,
  aggregatedKeyInfo: string,
  maxHistoryLength: number,
  channel: ApiChannel,
  model: ModelConfig,
  currentStatusPanelContentString: string,
  corsProxyUrl?: string,
  requestTimeout?: number
): Promise<{ response: AiScenarioAndKeyInfoResponse, fullPrompt: string }> {
    const gameHistoryString = formatGameHistoryForPrompt(currentHistory.slice(-maxHistoryLength));
    const fullPrompt = NARRATIVE_AND_KEY_INFO_GENERATION_TEMPLATE
        .replace('{scenarioGenerationPrompt}', aiPrompts.scenarioGenerationPrompt)
        .replace('{keyInfoExtractionPrompt}', aiPrompts.keyInfoExtractionPrompt)
        .replace('{gameBackgroundStory}', rulebook.backgroundStory)
        .replace('{gameRules}', rulebook.rules)
        .replace('{currentStatusPanelContentString}', currentStatusPanelContentString)
        .replace('{aggregatedKeyInfo}', aggregatedKeyInfo || "暂无关键信息。")
        .replace('{maxHistoryLengthToDisplay}', String(maxHistoryLength))
        .replace('{gameHistoryString}', gameHistoryString || "这是游戏的开始。")
        .replace('{playerAction}', playerAction);

    const responseData = await fetchWithAuth(channel, model, [{ role: 'user', content: fullPrompt }], undefined, false, undefined, corsProxyUrl, requestTimeout);
    const parsed = parseJsonFromAiResponse(responseData, ["narrative", "options", "keyInfoUpdate"]);

    if (!parsed.narrative || !Array.isArray(parsed.options) || typeof parsed.keyInfoUpdate !== 'string') {
        console.error("Double call (narrative) response missing required fields:", parsed);
        throw new Error("AI返回的叙事JSON结构不完整或无效。");
    }

    return { response: parsed as AiScenarioAndKeyInfoResponse, fullPrompt };
}

export async function updateStatusPanel(
  playerAction: string,
  currentNarrative: string,
  rulebook: Rulebook,
  aiPrompts: AiPrompts,
  channel: ApiChannel,
  model: ModelConfig,
  currentStatusPanelContentString: string,
  corsProxyUrl?: string,
  requestTimeout?: number
): Promise<{ response: DynamicGameState, fullPrompt: string }> {
    const fullPrompt = STATUS_PANEL_UPDATE_TEMPLATE
        .replace('{statusPanelPrompt}', aiPrompts.statusPanelPrompt)
        .replace('{gameBackgroundStory}', rulebook.backgroundStory)
        .replace('{gameRules}', rulebook.rules)
        .replace('{currentNarrative}', currentNarrative)
        .replace('{playerAction}', playerAction)
        .replace('{currentStatusPanelContentString}', currentStatusPanelContentString);

    const responseData = await fetchWithAuth(channel, model, [{ role: 'user', content: fullPrompt }], undefined, false, undefined, corsProxyUrl, requestTimeout);
    const parsed = parseJsonFromAiResponse(responseData);

    if (typeof parsed !== 'object' || parsed === null) {
        console.error("Status panel update response was not a JSON object:", parsed);
        throw new Error("AI返回的状态面板内容不是有效的JSON对象。");
    }

    return { response: parsed as DynamicGameState, fullPrompt };
}

export async function generateNarrativeOnly(
    currentHistory: GameTurn[],
    playerAction: string,
    rulebook: Rulebook,
    aiPrompts: AiPrompts,
    aggregatedKeyInfo: string,
    maxHistoryLength: number,
    channel: ApiChannel,
    model: ModelConfig,
    currentStatusPanelContentString: string,
    corsProxyUrl?: string,
    requestTimeout?: number
): Promise<{ response: AiScenarioResponse, fullPrompt: string }> {
    const gameHistoryString = formatGameHistoryForPrompt(currentHistory.slice(-maxHistoryLength));
    const fullPrompt = NARRATIVE_GENERATION_TEMPLATE
        .replace('{scenarioGenerationPrompt}', aiPrompts.scenarioGenerationPrompt)
        .replace('{gameBackgroundStory}', rulebook.backgroundStory)
        .replace('{gameRules}', rulebook.rules)
        .replace('{currentStatusPanelContentString}', currentStatusPanelContentString)
        .replace('{aggregatedKeyInfo}', aggregatedKeyInfo || "暂无关键信息。")
        .replace('{maxHistoryLengthToDisplay}', String(maxHistoryLength))
        .replace('{gameHistoryString}', gameHistoryString || "这是游戏的开始。")
        .replace('{playerAction}', playerAction);

    const responseData = await fetchWithAuth(channel, model, [{ role: 'user', content: fullPrompt }], undefined, false, undefined, corsProxyUrl, requestTimeout);
    const parsed = parseJsonFromAiResponse(responseData, ["narrative", "options"]);

    if (!parsed.narrative || !Array.isArray(parsed.options)) {
        console.error("Triple call (narrative) response missing required fields:", parsed);
        throw new Error("AI返回的叙事JSON结构不完整或无效。");
    }

    return { response: parsed as AiScenarioResponse, fullPrompt };
}

export async function extractKeyInfo(
    playerAction: string,
    currentNarrative: string,
    rulebook: Rulebook,
    aiPrompts: AiPrompts,
    channel: ApiChannel,
    model: ModelConfig,
    currentStatusPanelContentString: string,
    corsProxyUrl?: string,
    requestTimeout?: number
): Promise<{ response: string, fullPrompt: string }> {
    const fullPrompt = KEY_INFO_EXTRACTION_TEMPLATE
        .replace('{keyInfoExtractionPrompt}', aiPrompts.keyInfoExtractionPrompt)
        .replace('{gameBackgroundStory}', rulebook.backgroundStory)
        .replace('{gameRules}', rulebook.rules)
        .replace('{currentNarrative}', currentNarrative)
        .replace('{playerAction}', playerAction)
        .replace('{currentStatusPanelContentString}', currentStatusPanelContentString);

    const responseData = await fetchWithAuth(channel, model, [{ role: 'user', content: fullPrompt }], undefined, false, undefined, corsProxyUrl, requestTimeout);
    const textResult = parseJsonFromAiResponse(responseData);

    if (typeof textResult !== 'string') {
        console.warn("Key info extraction did not return plain text. Coercing to string. Response:", textResult);
        return { response: JSON.stringify(textResult), fullPrompt };
    }

    return { response: textResult.trim(), fullPrompt };
}


// --- Hybrid and Calculation Helper AI Mode Logic ---

const nativeMacroFunctions: { [key: string]: (args: Record<string, any>) => Record<string, any> } = {
  random: (args) => {
    try {
        const { min, max, success_threshold } = args;
        const roll = Math.floor(Math.random() * (Number(max) - Number(min) + 1)) + Number(min);
        const success = roll <= Number(success_threshold);
        return { ...args, roll, success }; // Return all args plus results
    } catch(e) {
        return { error: `随机数计算错误: ${e.message}` };
    }
  },
  clamp: (args) => {
    try {
        const { value, min, max } = args;
        const numValue = Number(value);
        const numMin = Number(min);
        const numMax = Number(max);
        if (isNaN(numValue) || isNaN(numMin) || isNaN(numMax)) {
            throw new Error("所有参数必须是数字。");
        }
        const result = Math.max(numMin, Math.min(numValue, numMax));
        return { ...args, result };
    } catch(e) {
        return { error: `范围限制计算错误: ${e.message}` };
    }
  },
  roll_dice: (args) => {
    try {
        const { dice } = args; // e.g., "2d6+3", "d20-1"
        if (typeof dice !== 'string') throw new Error("dice参数必须是字符串。");

        const cleanedDice = dice.toLowerCase().replace(/\s+/g, '');
        // Updated regex to handle missing number of dice like "d20"
        const match = cleanedDice.match(/(\d*)d(\d+)([+-]\d+)?/);
        if (!match) throw new Error(`无效的骰子表达式: ${dice}`);
        
        const numDice = match[1] ? parseInt(match[1], 10) : 1;
        const numSides = parseInt(match[2], 10);
        const modifier = match[3] ? parseInt(match[3], 10) : 0;
        
        if (numDice > 100 || numSides > 1000 || numDice < 1 || numSides < 1) throw new Error("骰子数量或面数无效。");

        let total = 0;
        for (let i = 0; i < numDice; i++) {
            total += Math.floor(Math.random() * numSides) + 1;
        }
        const result = total + modifier;
        
        return { ...args, result, total, modifier };
    } catch (e) {
        return { error: `掷骰计算错误: ${e.message}` };
    }
  }
};

const MACRO_TIMEOUT = 500; // 500ms timeout for macros

const macroWorkerCode = `
self.onmessage = (event) => {
    const { logic, args, state } = event.data;
    try {
        // We create a function that takes 'args' and 'state' as its parameters.
        // The user's code will be the body of this function.
        // It runs in the worker's isolated global scope.
        const userFunction = new Function('args', 'state', logic);
        const result = userFunction(args, state);
        self.postMessage({ success: true, result: result });
    } catch (e) {
        self.postMessage({ success: false, error: e.message });
    }
};
`;

let workerUrl: string | null = null;
function getWorkerUrl() {
    if (!workerUrl) {
        const workerBlob = new Blob([macroWorkerCode], { type: 'application/javascript' });
        workerUrl = URL.createObjectURL(workerBlob);
    }
    return workerUrl;
}

function formatOutput(template: string, context: Record<string, any>): string {
    const resolvePath = (object: any, path: string): any => {
        try {
            return path.split('.').reduce((o, p) => (o ? o[p] : undefined), object);
        } catch(e) {
            return undefined;
        }
    }

    return template.replace(/\$\{([^}]+)\}/g, (match, key) => {
        const value = resolvePath(context, key.trim());

        if (value !== undefined && value !== null) {
            if (typeof value === 'boolean') {
                return value ? '成功' : '失败';
            }
            if (typeof value === 'object') {
                return JSON.stringify(value); // Avoid showing [Object object]
            }
            return String(value);
        }
        return match; // Keep placeholder if path not found
    });
}

export async function executeMacroFunctions(
    functionCalls: GuidingAiPlan['function_calls'], 
    macroDefinitions: MacroFunction[],
    currentState: DynamicGameState,
    allowStateChanges: boolean = true
): Promise<{ resultsString: string, patches: JsonPatchOperation[], individualResults: {name: string, args: Record<string, any>, result: string}[] }> {
  if (!functionCalls || functionCalls.length === 0) {
    return { resultsString: "无函数调用被执行。", patches: [], individualResults: [] };
  }

  const allPatches: JsonPatchOperation[] = [];
  const individualResults: {name: string, args: Record<string, any>, result: string}[] = [];

  const results = await Promise.all(functionCalls.map(async (call): Promise<string> => {
    const definition = macroDefinitions.find(m => m.name === call.name);
    if (!definition) {
      const errorMsg = `错误: 未找到名为 '${call.name}' 的函数定义。`;
      individualResults.push({name: call.name, args: call.args, result: errorMsg});
      return errorMsg;
    }
    if (!definition.isEnabled) {
        const errorMsg = `错误: 函数 '${call.name}' 当前已被禁用。`;
        individualResults.push({name: call.name, args: call.args, result: errorMsg});
        return errorMsg;
    }

    let resultContext: Record<string, any> = { ...call.args };

    try {
      if (definition.isNative) {
        const nativeFunc = nativeMacroFunctions[definition.logic];
        if (nativeFunc) {
          const nativeResult = nativeFunc(call.args);
          resultContext = { ...resultContext, ...nativeResult };
        } else {
          throw new Error(`未找到名为 '${definition.logic}' 的原生函数实现。`);
        }
      } else {
        // Non-native: execute in web worker
        const worker = new Worker(getWorkerUrl());
        let formulaResult: any;
        try {
            const executionPromise = new Promise((resolve, reject) => {
                const timer = setTimeout(() => {
                    reject(new Error(`执行超时 (超过 ${MACRO_TIMEOUT}ms)。可能存在无限循环。`));
                }, MACRO_TIMEOUT);

                worker.onmessage = (e) => {
                    clearTimeout(timer);
                    if (e.data.success) {
                        resolve(e.data.result);
                    } else {
                        reject(new Error(e.data.error));
                    }
                };
                worker.onerror = (e) => {
                    clearTimeout(timer);
                    reject(new Error(`Worker 错误: ${e.message}`));
                };
            });
            
            worker.postMessage({ logic: definition.logic, args: call.args, state: currentState });
            formulaResult = await executionPromise;
            
        } finally {
            worker.terminate();
        }

        let outputValue = formulaResult;
        // Check if the result contains a patch for state modification
        if (typeof formulaResult === 'object' && formulaResult !== null && formulaResult.patch) {
            if (allowStateChanges && Array.isArray(formulaResult.patch)) {
                allPatches.push(...formulaResult.patch);
            }
            outputValue = formulaResult.result; // The value for the template is in the 'result' property
        }
        resultContext.result = outputValue;
      }
      
      if (resultContext.error) {
          throw new Error(String(resultContext.error));
      }
      const formattedResult = formatOutput(definition.outputTemplate, resultContext);
      individualResults.push({name: call.name, args: call.args, result: formattedResult });
      return formattedResult;

    } catch (e) {
      const errorMsg = `执行函数 '${call.name}' 时出错: ${e instanceof Error ? e.message : String(e)}`;
      individualResults.push({name: call.name, args: call.args, result: errorMsg});
      return errorMsg;
    }
  }));

  return { 
      resultsString: `函数执行结果:\n${results.join('\n')}`,
      patches: allowStateChanges ? allPatches : [],
      individualResults
    };
}


export function formatFunctionsForPrompt(macros: MacroFunction[], scope?: MacroScope): string {
  const filteredMacros = macros.filter(m => m.isEnabled && (scope ? m.scopes.includes(scope) : true));
  if (!filteredMacros || filteredMacros.length === 0) return "无可用函数。";
  
  return filteredMacros.map(m => {
    const params = m.parameters.map(p => `${p.name}`).join(', ');
    return `- ${m.name}(${params}): ${m.description}`;
  }).join('\n');
}

// FIX: Added optional `calcHelperSystemPrompt` parameter to match its usage in GameScreen.tsx.
export async function generateGuidingAiPlan(
  currentHistory: GameTurn[],
  playerAction: string,
  rulebook: Rulebook,
  aggregatedKeyInfo: string,
  maxHistoryLength: number,
  channel: ApiChannel,
  model: ModelConfig,
  currentStatusPanelContentString: string,
  macroFunctions: MacroFunction[],
  corsProxyUrl?: string,
  requestTimeout?: number,
  calcHelperSystemPrompt?: string
): Promise<GuidingAiPlan> {
  const functionListString = formatFunctionsForPrompt(macroFunctions, MacroScope.Planning);
  const userMessages = [{
    role: 'user', content: (calcHelperSystemPrompt || CALCULATION_HELPER_AI_TEMPLATE)
      .replace('{functionList}', functionListString)
      .replace('{gameBackgroundStory}', rulebook.backgroundStory)
      .replace('{gameRules}', rulebook.rules)
      .replace('{currentStatusPanelContentString}', currentStatusPanelContentString)
      .replace('{aggregatedKeyInfo}', aggregatedKeyInfo || "暂无关键信息。")
      .replace('{maxHistoryLengthToDisplay}', String(maxHistoryLength))
      .replace('{gameHistoryString}', formatGameHistoryForPrompt(currentHistory.slice(-maxHistoryLength)) || "这是游戏的开始。")
      .replace('{playerAction}', playerAction)
  }];

  try {
    const responseData = await fetchWithAuth(channel, model, userMessages, undefined, false, undefined, corsProxyUrl, requestTimeout);
    const parsed = parseJsonFromAiResponse(responseData, ["short_prompt", "function_calls"]);
    if (parsed.short_prompt && Array.isArray(parsed.function_calls)) {
      return parsed as GuidingAiPlan;
    }
    console.error("AI返回的规划助手数据结构不完整:", parsed);
    throw new Error("AI返回的规划助手数据结构不完整。");
  } catch (error) {
    console.error("生成AI规划时出错:", error);
    if (error instanceof Error) throw error;
    throw new Error("生成AI规划失败，发生未知错误。");
  }
}

export async function generateNarrativeForHybridMode(
    channel: ApiChannel,
    model: ModelConfig,
    basePromptTemplate: string,
    promptParams: Record<string, string>,
    macroFunctions: MacroFunction[],
    useStreaming: boolean,
    corsProxyUrl?: string,
    requestTimeout?: number
): Promise<string | AsyncGenerator<string>> {
    const functionListString = formatFunctionsForPrompt(macroFunctions, MacroScope.Narrative);
    let systemPrompt = basePromptTemplate.replace('{functionList}', functionListString);
    for (const key in promptParams) {
        systemPrompt = systemPrompt.replace(`{${key}}`, promptParams[key]);
    }
    
    const messages = [{ role: 'user', content: systemPrompt }];

    if (useStreaming) {
        return fetchStreamWithAuth(channel, model, messages, corsProxyUrl, requestTimeout);
    } else {
        const responseTextOrJson = await fetchWithAuth(channel, model, messages, undefined, false, undefined, corsProxyUrl, requestTimeout);
        const result = parseJsonFromAiResponse(responseTextOrJson);
        if (typeof result === 'string') {
          return result.trim();
        }
        console.warn("Hybrid Narrative (Non-Stream): API did not return plain text. Result:", result);
        return "错误：AI未按预期返回纯文本。";
    }
}

// FIX: Added optional `statusSystemPrompt` parameter to match its usage in GameScreen.tsx.
export async function generateHybridStructuredData(
  playerAction: string,
  currentNarrative: string,
  aiPrompts: AiPrompts,
  rulebook: Rulebook,
  channel: ApiChannel,
  model: ModelConfig,
  currentStatusPanelContentString: string,
  macroFunctions: MacroFunction[],
  corsProxyUrl?: string,
  requestTimeout?: number,
  statusSystemPrompt?: string
): Promise<AiHybridStructuredDataResponse> {
  const functionListString = formatFunctionsForPrompt(macroFunctions, MacroScope.Status);
  const systemPrompt = (statusSystemPrompt || HYBRID_STRUCTURED_DATA_TEMPLATE)
    .replace('{functionList}', functionListString)
    .replace('{gameBackgroundStory}', rulebook.backgroundStory)
    .replace('{gameRules}', rulebook.rules)
    .replace('{statusPanelPrompt}', aiPrompts.statusPanelPrompt)
    .replace('{keyInfoExtractionPrompt}', aiPrompts.keyInfoExtractionPrompt)
    .replace('{playerAction}', playerAction)
    .replace('{currentNarrative}', currentNarrative)
    .replace('{currentStatusPanelContentString}', currentStatusPanelContentString);

  try {
    const responseData = await fetchWithAuth(channel, model, [{role: 'user', content: systemPrompt}], undefined, false, undefined, corsProxyUrl, requestTimeout);
    const parsed = parseJsonFromAiResponse(responseData, ["options", "statusPanelPatch", "keyInfoUpdate"]);
    
    if (parsed.options && Array.isArray(parsed.options) && Array.isArray(parsed.statusPanelPatch) && typeof parsed.keyInfoUpdate === 'string') {
      return parsed as AiHybridStructuredDataResponse;
    }
    console.error("AI返回的混合模式结构化数据不完整:", parsed);
    throw new Error("AI返回的混合模式结构化数据不完整。");
  } catch (error) {
    console.error("生成混合模式结构化数据时出错:", error);
    if (error instanceof Error) throw error;
    throw new Error("生成混合模式结构化数据失败，发生未知错误。");
  }
}

// --- New Function Calling Mode Logic ---

export function formatMacrosForToolCalling(macros: MacroFunction[]): any[] {
    const toolMacros = macros.filter(m => m.isEnabled && m.scopes.includes(MacroScope.Tool));

    return toolMacros.map(m => {
        const properties: { [key: string]: JSONSchema7 } = {};
        m.parameters.forEach(p => {
            properties[p.name] = {
                type: 'string', // Default to string as AI is good at inference, simplifies config
                description: p.description,
            };
        });

        return {
            name: m.name,
            description: m.description,
            parameters: {
                type: 'object',
                properties: properties,
                required: m.parameters.map(p => p.name),
            },
        };
    });
}

// FIX: Added optional `systemPromptArg` parameter to match its usage in GameScreen.tsx.
export async function generateTurnWithFunctionCalling(
    currentHistory: GameTurn[],
    playerAction: string,
    rulebook: Rulebook,
    aiPrompts: AiPrompts,
    aggregatedKeyInfo: string,
    maxHistoryLength: number,
    channel: ApiChannel,
    model: ModelConfig,
    currentStatusPanelContentString: string,
    macroFunctions: MacroFunction[],
    currentState: DynamicGameState,
    onUpdate: TurnUpdateCallback,
    corsProxyUrl?: string,
    requestTimeout?: number,
    systemPromptArg?: string
): Promise<FunctionCallingTurnResponse & { functionCallHistory: GameTurn['functionCallHistory'] }> {

    const recentHistory = currentHistory.slice(-maxHistoryLength);
    const gameHistoryString = formatGameHistoryForPrompt(recentHistory);

    const narrativePrinciples = aiPrompts.functionCallingNarrativePrompt || aiPrompts.scenarioGenerationPrompt || "请生成有趣且连贯的叙事。";

    const systemPrompt = (systemPromptArg || TOOL_CALLING_TEMPLATE)
      .replace('{narrative_principles}', narrativePrinciples);
      
    const userPromptContent = `
游戏核心设定:
背景故事:
${rulebook.backgroundStory}
核心规则:
${rulebook.rules}

当前状态面板内容:
${currentStatusPanelContentString}

当前累计关键信息:
${aggregatedKeyInfo || "暂无关键信息。"}

最近的游戏历史:
${gameHistoryString || "这是游戏的开始。"}

玩家在本回合的行动/选择是: "${playerAction}"

请根据以上信息开始生成回合。
`;

    const messages: any[] = [
        { role: 'system', content: systemPrompt },
        { role: 'user', content: userPromptContent }
    ];
    
    const tools = formatMacrosForToolCalling(macroFunctions);

    const MAX_TOOL_CALLS = 10;
    let toolCallCount = 0;
    const functionCallHistory: GameTurn['functionCallHistory'] = [];
    let stateForThisTurn = JSON.parse(JSON.stringify(currentState));

    while (toolCallCount < MAX_TOOL_CALLS) {
        const responseData = await fetchWithAuth(channel, model, messages, tools, false, undefined, corsProxyUrl, requestTimeout);
        const choice = responseData.choices?.[0];

        if (!choice || !choice.message) {
            throw new Error("AI返回了无效的响应结构。");
        }

        const message = choice.message;
        messages.push(message); // Add AI's response to history

        if (message.tool_calls && message.tool_calls.length > 0) {
            const toolCalls = message.tool_calls;
            const functionNames = toolCalls.map((tc: any) => tc.function.name).join(', ');
            onUpdate({ type: 'task', content: `AI 正在调用函数: ${functionNames}...`});

            const functionCallsForExecution = toolCalls.map((tc: any) => {
                let args = {};
                try {
                    args = JSON.parse(tc.function.arguments || '{}');
                } catch(e) {
                    console.error(`无法解析函数 '${tc.function.name}' 的参数:`, tc.function.arguments);
                    throw new Error(`AI为函数 '${tc.function.name}' 提供了无效的参数JSON。`);
                }
                return { name: tc.function.name, args: args };
            });

            onUpdate({ type: 'task', content: '正在本地执行计算...'});
            const { individualResults, patches } = await executeMacroFunctions(functionCallsForExecution, macroFunctions, stateForThisTurn, true);
            
            if (patches.length > 0) {
                stateForThisTurn = applyJsonPatch(stateForThisTurn, patches);
            }

            individualResults.forEach(result => {
                onUpdate({ type: 'function_result', content: result.result });
                functionCallHistory.push(result); // Store for debug log
            });

            for (let i = 0; i < toolCalls.length; i++) {
                const toolCall = toolCalls[i];
                const result = individualResults.find(r => r.name === toolCall.function.name) || { result: '错误: 未找到执行结果。', args: {} };
                messages.push({
                    role: 'tool',
                    tool_call_id: toolCall.id,
                    content: result.result
                });
            }
            toolCallCount++;
        } else if (message.content) {
            // This is the final response
            onUpdate({ type: 'task', content: 'AI 正在生成最终叙事...'});
            const finalContent = message.content;
            const jsonRegex = /```json\s*([\s\S]*?)\s*```/;
            const match = finalContent.match(jsonRegex);

            if (match && match[1]) {
                const narrative = finalContent.substring(0, match.index).trim();
                const jsonStr = match[1];
                try {
                    const structuredData = JSON.parse(jsonStr) as AiHybridStructuredDataResponse;
                    return { narrative, structuredData, functionCallHistory };
                } catch (e) {
                    throw new ParseError(`无法解析AI在最终响应中提供的JSON块。`, jsonStr);
                }
            } else {
                throw new ParseError("AI的最终响应不包含预期的JSON数据块。", finalContent);
            }
        } else {
            // No content and no tool calls, something went wrong
            throw new Error("AI响应既不包含内容也不包含工具调用。");
        }
    }

    throw new Error(`已达到最大工具调用次数 (${MAX_TOOL_CALLS})，中止回合以防止无限循环。`);
}